<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>7模版方式模式</title>
  </head>
  <body>
    <strong>
      模板方法模式是一种只需使用继承就可以实现的非常简单的模式。
      模板方法模式由两部分结构组成，第一部分是抽象父类，第二部分是具体的实现子类 .`通常
      在抽象父类中封装了子类的算法框架，包括实现一些公共方法以及封装子类中所有方法的执行顺
      序。子类通过继承这个抽象类，也继承了整个算法结构，并且可以选择重写父类的方法。 ` ，
    </strong>

    <h1>好莱坞原则: 把一个函数主动权交给另外一个函数 “别调用我们，我们会调用你</h1>
    <em>
      当我们 用模板方法模式编写一个程序时，就意味着子类放弃了对自己的控制权，而是改为父类通知子类，
      哪些方法应该在什么时候被调用。作为子类，只负责提供一些设计上的细节。
    </em>

    <i>
       发布—订阅模式 在发布—订阅模式中，发布者会把消息推送给订阅者，这取代了原先不断去 fetch 消息的形式。
      例如假设我们乘坐出租车去一个不了解的地方，除了每过 5 秒钟就问司机“是否到达目的地”之
      外，还可以在车上美美地睡上一觉，然后跟司机说好，
    </i>
    <b>
       回调函数 在 ajax 异步请求中，由于不知道请求返回的具体时间，而通过轮询去判断是否返回数据，这
      显然是不理智的行为。所以我们通常会把接下来的操作放在回调函数中，传入发起 ajax 异步请求
      的函数。当数据返回之后，这个回调函数才被执行，这也是好莱坞原则的一种体现。把需要执行
      的操作封装在回调函数里，然后把主动权交给另外一个函数。至于回调函数什么时候被执行，则 是另外一个函数控制的。
    </b>
    <script>
      // js中的继承是构造函数原型的继承
      function myExtend(child, father) {
        child.prototype = Object.create(father.prototype);
        Object.defineProperty(child, 'constructor', {
          value: child,
          enumerable: false,
        });
      }

      // 父类
      const Beverage = function () {};
      Beverage.prototype.boilWater = function () {
        console.log('把水煮沸');
      };
      Beverage.prototype.brew = function () {
        throw new Error('子类必须重写父类方法');
      }; // 空方法，应该由子类重写
      Beverage.prototype.pourInCup = function () {
        throw new Error('子类必须重写父类方法');
      }; // 空方法，应该由子类重写
      Beverage.prototype.addCondiments = function () {
        throw new Error('子类必须重写父类方法');
      }; // 空方法，应该由子类重写

      Beverage.prototype.init = function () {
        this.boilWater();
        this.brew();
        this.pourInCup();
        if (this.customerWantsCondiments()) {
          this.addCondiments();
        }
      };

      // 子类
      const Coffee = function () {};
      // 继承
      // Coffee.prototype.__proto__ = Beverage.prototype;
      Coffee.prototype = Object.create(Beverage.prototype); // 等价于Coffee.prototype.__proto__ = Beverage.prototype;
      Object.defineProperty(Coffee.prototype, 'constructor', {
        value: child,
        enumerable: false,
      });

      Coffee.prototype.brew = function () {
        console.log('用沸水冲泡咖啡');
      };
      Coffee.prototype.pourInCup = function () {
        console.log('把咖啡倒进杯子');
      };
      Coffee.prototype.addCondiments = function () {
        console.log('加糖和牛奶');
      };
      Coffee.prototype.customerWantsCondiments = function (boolean = true) {
        return boolean;
      };

      const Coffee = new Coffee();
      Coffee.init();

      // 至此我们的 Coffee 类已经完成了，当调用 coffee 对象的 init 方法时，由于 coffee 对象和
      // Coffee 构造器的原型 prototype 上都没有对应的 init 方法，所以该请求会顺着原型链，被委托给
      // Coffee 的“父类”Beverage 原型上的 init 方法。

      // 在 Web 开发中也能找到很多模板方法模式的适用场景，比如我们在构建一系列的 UI 组件，Hook原理:
      // 这些组件的构建过程一般如下所示：
      // (1) 初始化一个 div 容器；
      // (2) 通过 ajax 请求拉取相应的数据；
      // (3) 把数据渲染到 div 容器里面，完成组件的构造；
      // (4) 通知用户组件渲染完毕。
      // 我们看到，任何组件的构建都遵循上面的 4 步，其中第(1)步和第(4)步是相同的。第(2)步不
      // 同的地方只是请求 ajax 的远程地址，第(3)步不同的地方是渲染数据的方式。
      // 于是我们可以把这 4 个步骤都抽象到父类的模板方法里面，父类中还可以顺便提供第(1)步和
      // 第(4)步的具体实现。当子类继承这个父类之后，会重写模板方法里面的第(2)步和第(3)步。
    </script>
  </body>
</html>
